package jmine.tec.batch.domain;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import jmine.tec.persist.Versioned;
import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Comment;
import jmine.tec.persist.annotation.Constraint;
import jmine.tec.persist.annotation.DiscriminatorComment;
import jmine.tec.persist.annotation.Index;
import jmine.tec.persist.schema.api.IndexOrder;

import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.Type;

import bancosys.tec.component.Documentation;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;
import bancosys.tec.utils.date.Timestamp;

/**
 * Essa classe representa um conjunto de elementos que serão processados pelo componente kernel.phase (controle de fases).
 * <p>
 * Deve ser extendida para implementar os métodos {@link ProcessingUnit#addChildren(Object)} e {@link ProcessingUnit#getChildren()}. <br>
 * Ou seja, essa classe sem extensão não funcionará no controle de fases!
 * <p>
 * Uma unidade de processamento representa uma página a ser processada pelo controle de fases. <br>
 * As fases executáveis iteram sobre os elementos presentes na unidade de processamento, e executam a fase para cada elemento.
 * 
 * @author Frank Cara
 * @param <E> o elemento sobre o qual a fase atua.
 * @see bancosys.tec.kernel.phase.exec.ExecPhase
 * @see Process
 */
@Entity
@Table(name = "PROCESSING_UNIT")
@Alias("PROUNI")
@SequenceGenerator(name = "SEQ_PROUNI", sequenceName = "SEQ_PROUNI")
@DiscriminatorValue(value = "GENERICO")
@DiscriminatorComment("UNIDADE DE PROCESSAMENTO GENERICA")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING)
@Comment(table = "PROCESSING_UNIT", column = "DISCRIMINATOR", value = "IDENTIFICADOR DA UNIDADE DE PROCESSAMENTO.")
@Documentation("ARMAZENA A UNIDADE DE PROCESSAMENTO DE UM PROCESSO, QUE PODE SER COMPOSTA DE 1 OU N CARTEIRAS.")
@Constraint(suffix = "1")
public class ProcessingUnit<E extends Persistable & Versioned> extends PersistableBusinessObject {

    /**
     * Indica que o processamento ainda não começou.
     */
    public static final int NOT_STARETD = 0;

    /**
     * Indica que o processamento já foi iniciado.
     */
    public static final int STARETD = 1;

    /**
     * Indica que o processamento foi finalizado sem erros.
     */
    public static final int FINISHED = 2;

    /**
     * Indica que o processamento foi finalizado com erros.
     */
    public static final int ERROR = 3;

    private Long id;

    private Timestamp startTime;

    private Timestamp finishTime;

    private int status = NOT_STARETD;

    private Process process;

    /**
     * @return o id da instancia.
     */
    @Id
    @Column(name = "COD_PROCESS_UNIT")
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_PROUNI")
    @Documentation("CODIGO DA UNIDADE DE PROCESSAMENTO.")
    public Long getId() {
        return this.id;
    }

    /**
     * Seta o valor do id.
     * 
     * @param id o valor do id.
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @return o valor da hora de inicio do processamento da unidade.
     */
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Column(name = "TS_START")
    @Documentation("HORARIO INICIAL DE EXECUCAO DO PROCESSAMENTO DA UNIDADE")
    public Timestamp getStartTime() {
        return this.startTime;
    }

    /**
     * Seta o valor da hora de inicio do processamento da unidade.
     * 
     * @param startTime a hora de inicio do processamento da unidade.
     */
    public void setStartTime(Timestamp startTime) {
        if (this.startTime == null) {
            this.startTime = startTime;
        }
    }

    /**
     * @return a hora final do do processamento da unidade.
     */
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Column(name = "TS_FINISH")
    @Documentation("HORARIO FINAL DE EXECUCAO DO PROCESSAMENTO DA UNIDADE.")
    public Timestamp getFinishTime() {
        return this.finishTime;
    }

    /**
     * Seta a hora final do do processamento da unidade.
     * 
     * @param finishTime a hora final do do processamento da unidade.
     */
    public void setFinishTime(Timestamp finishTime) {
        if (this.finishTime == null || this.finishTime.before(finishTime)) {
            this.finishTime = finishTime;
        }
    }

    /**
     * @return o status do do processamento da unidade.
     */
    @Column(name = "STATUS", nullable = false)
    @Documentation("INDICA O ESTADO DO PROCESSAMENTO DA UNIDADE, QUE PODE SER: NAO INICIADO (0), INICIADO (1),"
            + "FINALIZADO SEM ERROS (2), FINALIZADO COM ERROS (3).")
    @Constraint(name = "CK_PROUNI_0", condition = "STATUS IN (0, 1, 2, 3)")
    public int getStatus() {
        return this.status;
    }

    /**
     * Seta o status do do processamento da unidade.
     * 
     * @param status o status do do processamento da unidade.
     */
    public void setStatus(int status) {
        this.status = status;
    }

    /**
     * @return Retorna o processa da unidade de processamanto..
     */
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "COD_PROCESS")
    @ForeignKey(name = "FK_PROC_PROUNI")
    @Index(columns = "COD_PROCESS", suffix = "0", orders = IndexOrder.ASC)
    @Documentation("CODIGO DO PROCESSO EXECUTADO.")
    public Process getProcess() {
        return this.process;
    }

    /**
     * Seta o processo da unidade de processamento.
     * 
     * @param process o processo.
     */
    public void setProcess(Process process) {
        this.process = process;
    }

    /**
     * Esse metodo deve ser sobre-escrito pela classe filho e retornar os elementos que devem ser processados. Se a classe ProcessingUnit
     * for instanciado e o metodo getChildren() for invocado, uma PersistenceException será lançada.
     * 
     * @return esse método nunca vai retornar nada, sempre vai lançar uma PersistenceException
     * @throw PersistenceException pois esse método deve ser sobre-escrito pela classe específica.
     */
    @Transient
    public List<E> getChildren() {
        throw new UnsupportedOperationException("Tentativa de recuperar os elementos da unidade de processamento na classe ProcessingUnit");
    }

    /**
     * Método genérico para adicionar um elemento na unidade de processamento. <br>
     * ATENÇÃO: Esse método deve ser sobrecarregado nas extensões de ProcessingUnit, pois nessa implementação emitem
     * UnsupportedOperationException!
     * 
     * @param element o elemento sendo adicionado.
     */
    public void addChildren(E element) {
        throw new UnsupportedOperationException("Tentativa de recuperar os elementos da unidade de processamento na classe ProcessingUnit");
    }

    /**
     * Contém o ID e o Process que compoe essa unidade de processamento.
     * 
     * @return a string representando o objeto.
     */
    @Override
    public String toString() {
        return "[" + this.getId() + "] Process:" + this.getProcess() + "";
    }

}
